package logger

import (
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"
)

// CustomRotator custom log rotator
type CustomRotator struct {
	currentFile *os.File
	logDir      string
	maxSize     int64
	maxBackups  int
	maxAge      int
	mu          sync.RWMutex // Add read-write lock to ensure concurrency safety
}

// NewCustomRotator creates and returns a custom log rotator
// logDir specifies log directory
// maxSize is the maximum size of a single log file (unit: MB)
// maxBackups is the maximum number of historical log files to keep
// maxAge is the number of days to keep historical logs
// The returned CustomRotator will perform log rotation and clean up expired or excessive backup files when thresholds are reached
func NewCustomRotator(logDir string, maxSize int, maxBackups int, maxAge int) *CustomRotator {
	return &CustomRotator{
		logDir:     logDir,
		maxSize:    int64(maxSize) * 1024 * 1024, // Convert to bytes
		maxBackups: maxBackups,
		maxAge:     maxAge,
	}
}

// Write implements io.Writer interface
func (r *CustomRotator) Write(p []byte) (n int, err error) {
	// Use finer-grained lock control, only lock when really needed
	r.mu.RLock()

	// Fast path: if file exists and no rotation needed, write directly
	if r.currentFile != nil {
		currentFileName := getLogFileName()
		currentFilePath := filepath.Join(r.logDir, currentFileName)

		// Use filesystem to check if file exists, instead of relying on file handle's Stat
		if _, err := os.Stat(currentFilePath); err == nil {
			fileInfo, err := r.currentFile.Stat()
			if err == nil && fileInfo.Size()+int64(len(p)) < r.maxSize {
				// File exists and won't exceed size limit, write directly
				n, err = r.currentFile.Write(p)
				r.mu.RUnlock()
				return n, err
			}
		}
	}
	r.mu.RUnlock()

	// Slow path: need to check rotation or create file
	r.mu.Lock()

	// Ensure log directory exists
	if err := os.MkdirAll(r.logDir, 0755); err != nil {
		r.mu.Unlock()
		return 0, err
	}

	// Check if rotation is needed
	if err := r.rotateIfNeeded(); err != nil {
		r.mu.Unlock()
		return 0, err
	}

	// Check if current file handle is valid, recreate if file is deleted
	if r.currentFile != nil {
		currentFileName := getLogFileName()
		currentFilePath := filepath.Join(r.logDir, currentFileName)

		// Use filesystem to check if file exists, instead of relying on file handle's Stat
		if _, err := os.Stat(currentFilePath); os.IsNotExist(err) {
			// File was deleted, close invalid handle and recreate
			r.currentFile.Close()
			r.currentFile = nil
		}
	}

	// If current file is empty, recreate file handle
	if r.currentFile == nil {
		currentFileName := getLogFileName()
		currentFilePath := filepath.Join(r.logDir, currentFileName)
		file, err := os.OpenFile(currentFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			r.mu.Unlock()
			return 0, err
		}
		r.currentFile = file
	}

	// Write to current file, release lock immediately after writing
	n, err = r.currentFile.Write(p)
	r.mu.Unlock()
	return n, err
}

// Sync implements synchronization
func (r *CustomRotator) Sync() error {
	r.mu.RLock()
	defer r.mu.RUnlock()

	if r.currentFile != nil {
		return r.currentFile.Sync()
	}
	return nil
}

// rotateIfNeeded checks and performs rotation
func (r *CustomRotator) rotateIfNeeded() error {
	currentFileName := getLogFileName()
	currentFilePath := filepath.Join(r.logDir, currentFileName)

	// If current file doesn't exist, create new file
	if r.currentFile == nil {
		file, err := os.OpenFile(currentFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			return err
		}
		r.currentFile = file
		return nil
	}

	// Check file size
	fileInfo, err := r.currentFile.Stat()
	if err != nil {
		return err
	}

	// If file size exceeds limit, perform rotation
	if fileInfo.Size() >= r.maxSize {
		if err := r.rotate(); err != nil {
			return err
		}
	}

	return nil
}

// rotate performs rotation operation
func (r *CustomRotator) rotate() error {
	if r.currentFile != nil {
		r.currentFile.Close()
		r.currentFile = nil
	}

	currentFileName := getLogFileName()
	currentFilePath := filepath.Join(r.logDir, currentFileName)

	// Rotate existing files
	if err := r.rotateExistingFiles(currentFileName); err != nil {
		return err
	}

	// Create new file
	file, err := os.OpenFile(currentFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	r.currentFile = file

	// Asynchronously clean up old files to avoid blocking main process
	go r.cleanupOldFiles()

	return nil
}

// rotateExistingFiles rotates existing files
func (r *CustomRotator) rotateExistingFiles(currentFileName string) error {
	// Rotate from highest number to avoid overwriting
	for i := r.maxBackups; i > 0; i-- {
		oldPath := filepath.Join(r.logDir, currentFileName+"."+strconv.Itoa(i))
		newPath := filepath.Join(r.logDir, currentFileName+"."+strconv.Itoa(i+1))

		// If file exists, rename it
		if _, err := os.Stat(oldPath); err == nil {
			if err := os.Rename(oldPath, newPath); err != nil {
				return err
			}
		}
	}

	// Rename current file to .1
	currentPath := filepath.Join(r.logDir, currentFileName)
	backupPath := currentPath + ".1"
	if _, err := os.Stat(currentPath); err == nil {
		if err := os.Rename(currentPath, backupPath); err != nil {
			return err
		}
	}

	return nil
}

// cleanupOldFiles cleans up old files
func (r *CustomRotator) cleanupOldFiles() {
	currentDate := time.Now().Format("20060102")
	files, err := os.ReadDir(r.logDir)
	if err != nil {
		return
	}

	// Clean up files by date and number
	for _, file := range files {
		if file.IsDir() {
			continue
		}

		filename := file.Name()
		// Check if exceeds retention days
		if !strings.HasPrefix(filename, currentDate) {
			fileDate := strings.Split(filename, ".")[0]
			if fileDateInt, err := strconv.Atoi(fileDate); err == nil {
				currentDateInt, _ := strconv.Atoi(currentDate)
				if currentDateInt-fileDateInt > r.maxAge {
					// Delete files that exceed retention days
					_ = os.Remove(filepath.Join(r.logDir, filename))
				}
			}
		}

		// Check if exceeds maximum backup count
		if strings.HasPrefix(filename, currentDate) && strings.Contains(filename, ".") {
			parts := strings.Split(filename, ".")
			if len(parts) >= 3 {
				if backupNum, err := strconv.Atoi(parts[2]); err == nil {
					if backupNum > r.maxBackups {
						// Delete files that exceed maximum backup count
						_ = os.Remove(filepath.Join(r.logDir, filename))
					}
				}
			}
		}
	}
}
